﻿using CodeGenerator.CSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modifier = CodeGenerator.CSharp.Modifiers;

namespace CodeAngel.Domain
{
    /// <summary>
    /// Generates the entity collection files for an EntityDef:
    ///     * [entity name].Collection.cs - for end developers to add custom behavior, business 
    ///                                    rules, etc. This file will only be created if it does 
    ///                                    not already exist.
    ///     * [entity name].Collection.designer.cs - for auto-generated code
    /// </summary>
    public static class EntityCollectionFilesGenerator
    {
        #region CreateEntityCollectionFilesAsync

        /// <summary>
        /// Creates and writes to disk the entity files for the specified EntityDef.
        /// If the designer file already exists, it will be overwritten.  If the develoer
        /// file already exists, it will NOT be overwritten.
        /// </summary>
        public static Task CreateEntityCollectionFilesAsync(this EntityDef thisED)
        {
            return Task.Factory.StartNew(() =>
            {
                var Tasks = new Task[] 
                { 
                    DeveloperEntityCollectionFileGenerator.WriteToDiskAsync(thisED),
                    DesignerEntityCollectionFileGenerator.WriteToDiskAsync(thisED)
                };
                Task.WaitAll(Tasks);
            });
        }

        #endregion

        #region DeveloperEntityCollectionFileGenerator

        /// <summary>
        /// File generator for the developer entity collection file.  Once created, it will not be regenerated.
        /// This file is for end developers to add custom behavior, business rules, etc.
        /// </summary>
        private class DeveloperEntityCollectionFileGenerator
        {
            #region WriteToDiskAsync

            /// <summary>
            /// Creates and writes to disk the developer entity file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            public static Task WriteToDiskAsync(EntityDef thisED)
            {
                return Task.Factory.StartNew(() =>
                {
                    var ThisFileGenerator = new DeveloperEntityCollectionFileGenerator();
                    ThisFileGenerator.WriteToDisk(thisED);
                });
            }

            #endregion

            #region WriteToDisk

            /// <summary>
            /// Creates and writes to disk the developer entity file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            private void WriteToDisk(EntityDef thisED)
            {
                var ThisFB = new FileBuilder();
                ThisFB.ProjectName = thisED.Domain.ProjectName;
                ThisFB.Directory = thisED.EntitiesDirectoryPath;
                ThisFB.FileName = thisED.FileNames.Collection();
                ThisFB.Namespace = thisED.Namespace;

                // Add the using namespaces for this file.
                ThisFB.UsingNamespaces.Add("System");
                ThisFB.UsingNamespaces.Add(thisED.Domain.FrameworkNamespace);

                // Add the class.
                ThisFB.CodeUnits.Add(new DeveloperEntityCollectionClassBuilder(thisED));

                // Write this file to disk - THIS FILE SHOULD NEVER BE OVERWRITTEN.
                ThisFB.WriteToDisk(OverwriteExistingFile.No);
            }

            #endregion

            #region DeveloperEntityClassBuilder

            /// <summary>
            /// Builds the develoer entity collection class.
            /// </summary>
            private class DeveloperEntityCollectionClassBuilder : ClassBuilder
            {
                #region Constructors

                public DeveloperEntityCollectionClassBuilder(EntityDef thisED)
                    : base(thisED.ClassNames.Collection(), CodeGenerator.CSharp.Modifiers.PublicPartial)
                {

                }

                #endregion

            }

            #endregion
        }

        #endregion

        #region DesignerEntityFileGenerator

        /// <summary>
        /// File generator for the auto-generated entity collection file.  This file will be re-created
        /// as the domain model is updated.
        /// </summary>
        private class DesignerEntityCollectionFileGenerator
        {
            #region WriteToDiskAsync

            /// <summary>
            /// Creates and writes to disk the Designer entity file. If this file already exists, 
            /// it will be overwritten.
            /// </summary>
            public static Task WriteToDiskAsync(EntityDef thisED)
            {
                return Task.Factory.StartNew(() =>
                {
                    var ThisFileGenerator = new DesignerEntityCollectionFileGenerator();
                    ThisFileGenerator.WriteToDisk(thisED);
                });
            }

            #endregion

            #region WriteToDisk

            /// <summary>
            /// Creates and writes to disk the Designer entity file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            private void WriteToDisk(EntityDef thisED)
            {
                var ThisFB = new FileBuilder();
                ThisFB.ProjectName = thisED.Domain.ProjectName;
                ThisFB.Directory = thisED.EntitiesDirectoryPath;
                ThisFB.FileName = thisED.FileNames.CollectionDesigner();
                ThisFB.Namespace = thisED.Namespace;
                ThisFB.IsAutoGenerated = true;

                // Add the using namespaces for this file.
                ThisFB.UsingNamespaces.Add("System");
                ThisFB.UsingNamespaces.Add(thisED.Domain.FrameworkNamespace);

                // Add the class.
                ThisFB.CodeUnits.Add(new DesignerEntityCollectionClassBuilder(thisED));

                // Write this file to disk.
                ThisFB.WriteToDisk(OverwriteExistingFile.Yes);
            }

            #endregion

            #region DesignerEntityCollectionClassBuilder

            /// <summary>
            /// Builds the auto-generated entity collection class.
            /// </summary>
            private class DesignerEntityCollectionClassBuilder : ClassBuilder
            {
                #region Constructors

                public DesignerEntityCollectionClassBuilder(EntityDef thisED)
                    : base(thisED.ClassNames.Collection(), CodeGenerator.CSharp.Modifiers.PublicPartial)
                {
                    this.BaseClass = "EntityCollection<" + thisED.Name + ">";
                    base.Documentation.Summary = "To represent a collection of " + thisED.Name + " entities.";
                    
                    this.AddConstrucorProcedures(thisED);
                    this.AddCreateNewProcedure(thisED);
                    this.AddAddNewProcedure(thisED);
                }

                #endregion

                #region AddConstrucorProcedures

                /// <summary>
                /// Adds the constructors.
                /// </summary>
                public void AddConstrucorProcedures(EntityDef thisED)
                {
                    var ThisRegion = this.AddRegion("Construcors");

                    var ConstructorWithAggregate = ThisRegion.AddConstructor(thisED.ClassNames.Collection(), Modifier.Internal);
                    ConstructorWithAggregate.Documentation.Summary = "Creates a new instance of this " + thisED.ClassNames.Collection() + " linked to the identified Aggregate.";
                    ConstructorWithAggregate.Parameters.Add("aggregate", "Aggregate");
                    ConstructorWithAggregate.Base = "aggregate";

                    var ConstructorWithAggregateAndParentEntity = ThisRegion.AddConstructor(thisED.ClassNames.Collection(), Modifier.Internal);
                    ConstructorWithAggregateAndParentEntity.Documentation.Summary = "Creates a new instance of this " + thisED.ClassNames.Collection() + " linked to the identified Aggregate and the identified parent entity.";
                    ConstructorWithAggregateAndParentEntity.Parameters.Add("aggregate", "Aggregate");
                    ConstructorWithAggregateAndParentEntity.Parameters.Add("parentEntity", "Entity");
                    ConstructorWithAggregateAndParentEntity.Base = "aggregate, parentEntity";
                }

                #endregion

                #region AddCreateNewProcedure

                /// <summary>
                /// Adds the CreateNew method, which is used to create a new entity.
                /// </summary>
                private void AddCreateNewProcedure(EntityDef thisED)
                {
                    var ThisRegion = this.AddRegion("CreateNew");
                    var ThisProcedure = ThisRegion.AddProcedure("CreateNew", Modifier.Public, thisED.ClassNames.ClassName());
                    ThisProcedure.Documentation.Summary = "Creates a new " + thisED.Name + " entity and returns a reference it. This " + thisED.Name + " entity will not be added to this collection.";
                    ThisProcedure.Body.Add("var NewEntity = new {0}(this.Aggregate, this.ParentEntity);", thisED.ClassNames.ClassName());
                    ThisProcedure.Body.Add("NewEntity.IsNew = true;");
                    ThisProcedure.Body.Add("NewEntity.InitializeAllProperties();");
                    ThisProcedure.Body.Add("NewEntity.SetDefaultPropertyValues();");
                    ThisProcedure.Body.Add("return NewEntity;");
                }

                #endregion

                #region AddAddNewProcedure

                /// <summary>
                /// Adds the AddNew method, which is used to create a new entity and add it to the current collection.
                /// </summary>
                private void AddAddNewProcedure(EntityDef thisED)
                {
                    var ThisRegion = this.AddRegion("AddNew");
                    var ThisProcedure = ThisRegion.AddProcedure("AddNew", Modifier.Public, thisED.Name);
                    ThisProcedure.Documentation.Summary = "Creates a new " + thisED.Name + " entity, adds it to this collection and returns a reference to it.";
                    ThisProcedure.Body.Add("var NewEntity = this.CreateNew();");
                    ThisProcedure.Body.Add("this.Add(NewEntity);");
                    ThisProcedure.Body.Add("return NewEntity;");

                }

                #endregion
            }

            #endregion

        }

        #endregion
    }
}
